package com.ryan.plugin;

import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.ConditionalExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.visitor.VoidVisitor;
import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.ryan.plugin.store.Store;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 解析java文件内的源代码
 */
public class ClassAnalyzer {
    private final Store store;
    private JavaSymbolSolver solver;

    public ClassAnalyzer() {
        store = Store.getInstance();
    }

    public void prepare(AnActionEvent event) {
        CustomCombinedTypeSolver typeSolver = genCombinedTypeSolver(event);
        solver = new JavaSymbolSolver(typeSolver);

        ParserConfiguration configuration = new ParserConfiguration()
                .setAttributeComments(false).setSymbolResolver(solver);
        StaticJavaParser.setConfiguration(configuration);
    }

    public void analyze(String path) {
        analyze(new File(path));
    }

    public void analyze(File file) {
        System.out.println("===================== start " + file.getAbsolutePath());
        try {
            CompilationUnit unit = StaticJavaParser.parse(file);

            // 找到java文件中的主要类的定义
            Optional<ClassOrInterfaceDeclaration> optional = unit.findFirst(ClassOrInterfaceDeclaration.class);
            if (optional.isEmpty()) return;
            ClassOrInterfaceDeclaration classDeclaration = optional.get();
            ResolvedReferenceTypeDeclaration resolvedClass = classDeclaration.resolve();

            replaceAllConditionExpr(unit);
            replaceAllStringConcatenation(unit);
            recordAllMethodCallExpr(unit, resolvedClass);

            VoidVisitor<Store> visitor = new Visitor(resolvedClass);
            visitor.visit(unit, store);

            findDirectAncestors(resolvedClass);

        } catch (FileNotFoundException e) {
            store.putException(file.getAbsolutePath(), "", "");
            e.printStackTrace();
        }

        System.out.println("===================== done " + file.getAbsolutePath());
    }

    /**
     * 替换语法树中的所有三目表达式为表达式的右侧结果, 避免意外的StackOverflowException
     * @param unit 语法数
     */
    private void replaceAllConditionExpr(CompilationUnit unit) {
        List<ConditionalExpr> conditions = unit.findAll(ConditionalExpr.class);
        for (ConditionalExpr condition : conditions) {
            if (condition.getParentNode().isPresent()) {
                Node parent = condition.getParentNode().get();
                parent.replace(condition, condition.getElseExpr());
            }
        }
    }

    /**
     * 替换语法树中的所有字符串拼接(string+any)为纯文本字符串, 避免意外的StackOverflowException
     * @param unit 语法数
     */
    private void replaceAllStringConcatenation(CompilationUnit unit) {
        List<BinaryExpr> binaryExprs = unit.findAll(BinaryExpr.class);
        for (BinaryExpr binaryExpr : binaryExprs) {
            if (binaryExpr.getParentNode().isPresent()) {
                AtomicBoolean isAdd = new AtomicBoolean(false);
                AtomicBoolean hasString = new AtomicBoolean(false);
                if (checkOperationIsStringConcatenation(binaryExpr, isAdd, hasString)) {
                    Node parent = binaryExpr.getParentNode().get();
                    parent.replace(binaryExpr, new StringLiteralExpr("just string"));

                }
            }
        }
    }

    /**
     * 判断表达式是否是字符串拼接(string+any)
     * @param be 表达式
     * @param isAdd 是否包含加号
     * @param hasString 表达式中是否包含任意一个字符串
     * @return 是否是字符串拼接(string+any)
     */
    private boolean checkOperationIsStringConcatenation(BinaryExpr be, AtomicBoolean isAdd, AtomicBoolean hasString) {
        ResolvedType type = null;
        try {
            type = solver.calculateType(be.getRight());
        } catch (Exception ignored) {}

        if (type instanceof ReferenceTypeImpl) {
            ReferenceTypeImpl impl = (ReferenceTypeImpl) type;
            if (impl.getQualifiedName().equals("java.lang.String")) {
                hasString.set(true);
            }
        }
        if (be.getOperator().asString().equals("+")) {
            isAdd.set(true);
        }

        if (isAdd.get() && hasString.get()) {
            return true;
        } else {
            if (be.getLeft() instanceof BinaryExpr) {
                return checkOperationIsStringConcatenation((BinaryExpr) be.getLeft(), isAdd, hasString);
            }
        }

        return false;
    }

    /**
     * 分析并记录语法树中的所有函数调用语句
     * @param unit 语法树
     * @param resolvedClass 所属类的定义
     */
    private void recordAllMethodCallExpr(CompilationUnit unit, ResolvedReferenceTypeDeclaration resolvedClass) {
        List<MethodCallExpr> list = unit.findAll(MethodCallExpr.class);
        for (MethodCallExpr methodCallExpr : list) {
            try {
                ResolvedMethodLikeDeclaration declaration = methodCallExpr.resolve();
                store.put(declaration.getPackageName(), declaration.getClassName(), declaration.getSignature());
            } catch (Exception e) {
                store.putException(resolvedClass.getPackageName(), resolvedClass.getClassName(), methodCallExpr.toString());
                System.err.println(methodCallExpr.toString());
            }
        }
    }

    /**
     * 找到java文件主要类的所有直接祖先
     * @param resolvedClass 所属类的定义
     */
    private void findDirectAncestors(ResolvedReferenceTypeDeclaration resolvedClass) {
        resolvedClass.getAncestors().forEach(r -> {
            if (!r.getQualifiedName().equals("java.lang.Object") && r.getTypeDeclaration().isPresent()) {
                ResolvedReferenceTypeDeclaration reference = r.getTypeDeclaration().get();
                store.putExtends(reference.getPackageName(), reference.getClassName(), reference.isInterface());
            }
        });
    }

    private void addJavaParserTypeSolver(AnActionEvent event, CustomCombinedTypeSolver solver) {
        Project project = event.getProject();
        VirtualFile[] files = ProjectRootManager.getInstance(Objects.requireNonNull(project)).getContentRoots();
        for (int i = 0; i < files.length; i++) {
            if (i > 0) {
                String path = files[i].getPath();
                path += "/src/main/java/";
                solver.add(new JavaParserTypeSolver(path));
            }
        }
    }

    private void addJarTypeSolver(AnActionEvent event, CustomCombinedTypeSolver solver) throws IOException {
        Project project = event.getProject();
        Module[] modules = ModuleManager.getInstance(Objects.requireNonNull(project)).getModules();

        for (Module module : modules) {
            VirtualFile[] files = ModuleRootManager.getInstance(module).orderEntries().classes().getRoots();
            for (VirtualFile file : files) {
                String url = file.getUrl();
                if (url.startsWith("jar://")) {
                    String path = file.getPath().replaceAll("!/", "");
                    solver.add(new JarTypeSolver(path));
                }
            }
        }
    }

    private CustomCombinedTypeSolver genCombinedTypeSolver(AnActionEvent event) {
        CustomCombinedTypeSolver solver = new CustomCombinedTypeSolver();
        try {
            addJavaParserTypeSolver(event, solver);
            addJarTypeSolver(event, solver);
        } catch (IOException e) {
            e.printStackTrace();
        }
        solver.add(new ReflectionTypeSolver());
        return solver;
    }
}
